package com.imooc.pan.server.common.task;


import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.imooc.pan.core.constants.RPanConstants;
import com.imooc.pan.schedule.ScheduleTask;
import com.imooc.pan.server.common.event.log.ErrorLogEvent;
import com.imooc.pan.server.modules.file.entity.RPanFileChunk;
import com.imooc.pan.server.modules.file.service.RPanFileChunkService;
import com.imooc.pan.storage.engine.core.StorageEngine;
import com.imooc.pan.storage.engine.core.context.DeleteFileContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.lang.management.ClassLoadingMXBean;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 过期分片清理任务
 * @author hhb
 * @data 2024/6/14 22:27
 */
@Component
@Slf4j
public class CleanExpireChunkFileTasK implements ScheduleTask, ApplicationContextAware {
    private static final Long BATCH_SIZE = 500L;

    @Autowired
    private RPanFileChunkService rPanFileChunkService;

    @Autowired
    private StorageEngine storageEngine;

    private ApplicationContext applicationContext;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }


    /**
     * 获取定时任务的名称
     * @return
     */
    @Override
    public String getName() {
        return "CleanExpireChunkFileTasK";
    }

    /**
     * 执行清理任务
     * 1。滚动查询过期的文件分片
     * 2.删除物理文件（委托存储引擎）
     * 3.删除分片记录
     * 4.重置上次查询的最大文件分片记录Id，继续滚动查询
     */
    @Override
    public void run() {
        log.info("{} start clean expire chunk file...",getName());
        List<RPanFileChunk> scrollerFileChunk;
        //滚动指针
        Long scrollPoint = 1L;
        do{
            scrollerFileChunk = QueryScrollerFileChunk(scrollPoint);
            if(CollectionUtils.isNotEmpty(scrollerFileChunk)){
                //删除
                deleteRealFileChunk(scrollerFileChunk);
                List<Long> idList = deleteFileChunkRecords(scrollerFileChunk);
                scrollPoint = Collections.max(idList);
            }
        }while (CollectionUtils.isNotEmpty(scrollerFileChunk));
    }

    /**********************************private**********************************/
    /**
     * 数据库删除过期的文件分片记录
     * @param scrollerFileChunk
     * @return
     */
    private List<Long> deleteFileChunkRecords(List<RPanFileChunk> scrollerFileChunk) {
        List<Long> idsList = scrollerFileChunk.stream().map(RPanFileChunk::getId).collect(Collectors.toList());
        rPanFileChunkService.removeByIds(idsList);
        return idsList;
    }

    /**
     * 委托文件引擎删除
     * @param scrollerFileChunk
     */
    private void deleteRealFileChunk(List<RPanFileChunk> scrollerFileChunk) {
        DeleteFileContext context = new DeleteFileContext();
        List<String> realPathList = scrollerFileChunk.stream().map(RPanFileChunk::getRealPath).collect(Collectors.toList());
        context.setRealPathList(realPathList);
        try {
            storageEngine.delete(context);
        } catch (IOException e) {
            saveErrorLog(realPathList);
        }
    }

    /**
     * 记录删除文件失败日志
     * @param realPathList
     */
    private void saveErrorLog(List<String> realPathList) {
        ErrorLogEvent errorLogEvent = new ErrorLogEvent(this, JSON.toJSONString(realPathList), RPanConstants.ZERO_LONG);
        applicationContext.publishEvent(errorLogEvent);
    }

    /**
     * 滚动查询要删除的文件分片
     * @param scrollPoint
     * @return
     */
    private List<RPanFileChunk> QueryScrollerFileChunk(Long scrollPoint) {
        LambdaQueryWrapper<RPanFileChunk> lqw = new LambdaQueryWrapper<>();
        //过期时间小于当前时间
        lqw.le(RPanFileChunk::getExpirationTime,new Date());
        //当前id大于或等于滚动指针
        lqw.ge(RPanFileChunk::getId,scrollPoint);
        lqw.last(" limit " + BATCH_SIZE);
        List<RPanFileChunk> list = rPanFileChunkService.list(lqw);
        return list;
    }

}
